package com.yonyou.pmclouds.basecom.dao.impl.mybatis;

import com.yonyou.pmclouds.basecom.dao.BaseMapper;
import com.yonyou.pmclouds.basecom.dao.impl.mybatis.modelParser.ColumnTarget;
import com.yonyou.pmclouds.basecom.dao.impl.mybatis.modelParser.ModelUtils;
import com.yonyou.pmclouds.basecom.dao.impl.mybatis.modelParser.Property;
import com.yonyou.pmclouds.basecom.dao.query.CustomQueryParam;
import com.yonyou.pmclouds.basecom.dao.query.NoValueQueryParam;
import com.yonyou.pmclouds.basecom.dao.query.WithValueQueryParam;
import com.yonyou.pmclouds.basecom.entity.Entity;
import com.yonyou.pmclouds.basecom.util.BeanHelper;
import com.yonyou.pmclouds.basecom.util.CommonFieldConst;
import com.yonyou.pmclouds.basecom.util.ReflectUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.annotation.ProviderContext;
import org.apache.ibatis.jdbc.SQL;

import javax.persistence.Table;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * 使用mybatis构造基础常用的sql语句
 * <br>
 * 暂时不要考虑缓存sql了，目前分析的结果是，mybatis中每一次都是这个类的新实例
 *
 * @author zm
 */
// TODO 去掉这个泛型，没啥用。。
@Slf4j
public class BaseSQLProvider<T extends Entity> {

    private String tableName;
    private Class<?> modelClass;
    private static ThreadLocal<Class<?>> threadModelClass = new ThreadLocal<Class<?>>();
    private static final String OPERATOR_EQUAL = " = ";
    private static final String OPERATOR_LIKE = " like ";

    private void initFromThreadLocal() {
        modelClass = BaseSQLProvider.threadModelClass.get();
        tableName = modelClass.getAnnotation(Table.class).name();

        BaseSQLProvider.threadModelClass.remove();
    }

    //TODO 可以不用这个方法了
    public static void setModelClass(Class<?> modelClass) {
        BaseSQLProvider.threadModelClass.set(modelClass);
    }

    public String getAll(ProviderContext context) {
        initFromThreadLocal();
        //TODO 使用context改造，不需要threadlocal，不需要切面 comment by mapeif
        Class<?> clz = (Class<?>) ((ParameterizedType) context.getMapperType().getGenericInterfaces()[0]).getActualTypeArguments()[0];
        SQL sql = SELECT_FROM();
        return sql.toString();
    }

    public String getById(ProviderContext context) {
        initFromThreadLocal();
        SQL sql = SELECT_FROM().WHERE(BeanHelper.getIdColName(modelClass)
                + " = #{" + BeanHelper.getIdFieldName(modelClass) + "}").WHERE(" dr=0 ");
        return sql.toString();
    }

    public String getByBillIds(ProviderContext context, Map<String, Object> dataMap) {
        initFromThreadLocal();
        String ids = (String)dataMap.get("ids");
        SQL sql = SELECT_FROM().WHERE(BeanHelper.getIdColName(modelClass)
                + " in (" + ids + ")").WHERE(" dr=0 ");
        return sql.toString();
    }

    public String getByIdWithOutDr(ProviderContext context) {
        initFromThreadLocal();
        SQL sql = SELECT_FROM().WHERE(BeanHelper.getIdColName(modelClass)
                + " = #{" + BeanHelper.getIdFieldName(modelClass) + "}");
        return sql.toString();
    }

    public String countGet(ProviderContext context, Map<String, Object> dataMap) {
        T findParams = (T) dataMap.get("findParams");
        initFromThreadLocal();
        SQL sql = COUNT_FROM();
        sql = WHERE(sql, findParams, OPERATOR_EQUAL);
        return sql.toString();
    }

    public String get(ProviderContext context, Map<String, Object> dataMap) {
        T findParams = (T) dataMap.get("findParams");
        initFromThreadLocal();
        SQL sql = SELECT_FROM();
        sql = WHERE(sql, findParams, OPERATOR_EQUAL);
        return sql.toString();
    }

    public String getIdsByPrj(ProviderContext context, Map<String, Object> dataMap) {

        initFromThreadLocal();
        SQL sql = SELECT_ID_FROM();
        sql.WHERE(CommonFieldConst.PK_PROJECT + OPERATOR_EQUAL + "'"+dataMap.get("pkProject")+"'");

        return sql.toString();
    }

    public String countFind(ProviderContext context, Map<String, Object> dataMap) {
        T findParams = (T) dataMap.get("findParams");
        fixParamsValueToLike(findParams);
        initFromThreadLocal();

        SQL sql = COUNT_FROM();
        sql = WHERE(sql, findParams, OPERATOR_LIKE);
        return sql.toString();
    }

    public String find(ProviderContext context, Map<String, Object> dataMap) {
        T findParams = (T) dataMap.get("findParams");
        fixParamsValueToLike(findParams);
        initFromThreadLocal();

        SQL sql = SELECT_FROM();
        sql = WHERE(sql, findParams, OPERATOR_LIKE);
        return sql.toString();
    }

    public String countQuery(ProviderContext context, Map<String, Object> customQueryParams) {
        initFromThreadLocal();
        SQL sql = COUNT_FROM();
        sql = WHERE_CUSTOM(sql, customQueryParams);
        return sql.toString();
    }

    public String query(ProviderContext context, Map<String, Object> dataMap) {
//        List<Sort> sortList = (List<Sort>) dataMap.get("sortList"); 
        initFromThreadLocal();
        SQL sql = SELECT_FROM();
        sql = WHERE_CUSTOM(sql, dataMap);
        return sql.toString();
    }

    public String insert(ProviderContext context, final T t) throws IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        initFromThreadLocal();
        // 设置默认值 
        if (t.pkFiledValue() == null || "".equals(t.pkFiledValue())) {
            t.initPkFiledValue(UUID.randomUUID().toString());
        }

        return new SQL() {
            {
                INSERT_INTO(tableName);

                Map<String, Property> properties = ModelUtils.getProperties(t, ColumnTarget.INSERT);
                for (Property property : properties.values()) {
                    // 过滤不允许更新的字段 
                    if (isIgnoreInsert(property, t)) {
                        continue;
                    }

                    VALUES(property.getColumnName(), "#{" + property.getName() + "}");
                }
            }
        }.toString();
    }

    public String batchInsert(ProviderContext context, Map<String, Object> dataMap) throws IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        initFromThreadLocal();

        final List<T> vos = (List<T>) dataMap.get("vos");
        for (int i = 0; i < vos.size(); i++) {
            // 设置默认值
            if (vos.get(i).pkFiledValue() == null || "".equals(vos.get(i).pkFiledValue())) {
                vos.get(i).initPkFiledValue(UUID.randomUUID().toString());
            }
        }
        StringBuilder batchInsertSQL = new StringBuilder();
        batchInsertSQL.append("insert into ").append(tableName);
        batchInsertSQL.append("(");

        String[] props = BeanHelper.getPersistPropOfClz(modelClass);
        String[] propClzs = new String[props.length];
        for (String prop : props) {
            batchInsertSQL.append(BeanHelper.getColName(modelClass, prop));
            batchInsertSQL.append(",");
        }
        for (int i = 0; i < props.length; i++) {
            propClzs[i] = BeanHelper.getFieldTypeFromClz(modelClass, props[i]).getName();
        }
        batchInsertSQL.deleteCharAt(batchInsertSQL.length() - 1);
        batchInsertSQL.append(")").append("values");

        for (int i = 0; i < vos.size(); i++) {
            batchInsertSQL.append("(");
            for (int j = 0; j < props.length; j++) {
                batchInsertSQL.append("#{vos[").append(i).append("].").append(props[j])
                        .append(", javaType=").append(propClzs[j]).append("}");
                if (j < props.length - 1) {
                    batchInsertSQL.append(",");
                }
            }
            batchInsertSQL.append(")");
            if (i < vos.size() - 1) {
                batchInsertSQL.append(",");
            }

        }

        return batchInsertSQL.toString();
    }

    public String delete(ProviderContext context, Object id) {
        initFromThreadLocal();
        return new SQL() {
            {
                DELETE_FROM(tableName);
                WHERE(BeanHelper.getIdColName(modelClass)
                        + " = #{" + BeanHelper.getIdFieldName(modelClass) + "}");
            }
        }.toString();
    }

    public String update(ProviderContext context, final T t) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
        initFromThreadLocal();

        return new SQL() {
            {
                UPDATE(tableName);

                String className = StringUtils.split(modelClass.getName(), "$")[0];
                try {
                    Map<String, Property> properties = ModelUtils.getProperties(Class.forName(className), ColumnTarget.UPDATE);

                    for (Property property : properties.values()) {
                        if (property.getName().equals("createTime")) {
                            continue;
                        }
                        SET(property.getColumnName() + " = #{" + property.getName() + "}");
                    }
                } catch (ClassNotFoundException e) {
                    log.error(e.getMessage(), e);
                }
                WHERE(BeanHelper.getIdColName(modelClass)
                        + " = #{" + BeanHelper.getIdFieldName(modelClass) + "}");
            }
        }.toString();
    }

    private boolean isIgnoreInsert(Property property, T t) {
        boolean isIgnore;
        try {
            isIgnore = property.isNullValue(t);
        } catch (Exception e) {
            isIgnore = true;
        }
        return isIgnore;
    }

    private void fixParamsValueToLike(T findParams) {
        PropertyDescriptor[] propertyDescriptors = ReflectUtils.getBeanSetters(findParams.getClass());
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            if (propertyDescriptor.getPropertyType() != String.class) {
                continue;
            }

            try {
                Object value = propertyDescriptor.getReadMethod().invoke(findParams);
                if (value != null) {
                    propertyDescriptor.getWriteMethod().invoke(findParams, "%" + value + "%");
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    private SQL COUNT_FROM() {
        return new SQL() {
            {
                SELECT("COUNT(*)");
                FROM(tableName);
            }
        };
    }

    private SQL SELECT_FROM() {
        final Map<String, Property> columns = ModelUtils.getProperties(modelClass, ColumnTarget.SELECT);
        return new SQL() {
            {
                for (Property property : columns.values()) {
                    SELECT(property.getColumnName());
                }
                FROM(tableName);
            }
        };
    }

    private SQL SELECT_ID_FROM() {
        final Map<String, Property> columns = ModelUtils.getProperties(modelClass, ColumnTarget.SELECT);
        return new SQL() {
            {
                SELECT( columns.get(BeanHelper.getIdFieldName(modelClass)).getColumnName()  );
                FROM(tableName);
            }
        };
    }

    private SQL WHERE(SQL sql, T findParams, String operator) {
        if (findParams == null) {
            return sql;
        }

        Map<String, Property> properties = ModelUtils.getProperties(findParams, ColumnTarget.WHERE);

        for (Property property : properties.values()) {
            if (operator.equalsIgnoreCase(OPERATOR_LIKE)) {
            }
            sql.WHERE(property.getColumnName() + operator + "#{findParams." + property.getName() + "}");
        }
        return sql;
    }

    private SQL WHERE_CUSTOM(SQL sql, Map<String, Object> dataMap) {
        if (dataMap == null) {
            return sql;
        }

        List<CustomQueryParam> customQueryParams = (List<CustomQueryParam>) dataMap.get("queryParams");
        if (customQueryParams == null) {
            return sql;
        }
        dataMap.clear();
        return WHERE_CUSTOM(sql, modelClass, dataMap, customQueryParams, null);
    }

    public static SQL WHERE_CUSTOM(SQL sql, Class<?> modelClass, Map<String, Object> dataMap, List<CustomQueryParam> customQueryParams, String tableAlias) {
        Map<String, Property> properties = ModelUtils.getProperties(modelClass, null);

//        int i = 0; 
        for (CustomQueryParam customQueryParam : customQueryParams) {
            String key = customQueryParam.getProperty();
            Property property = properties.get(key);
            if (property == null) {
                continue;
            }
            String condition = "";
            if (StringUtils.isNotEmpty(tableAlias)) {
                condition = tableAlias + "." + condition;
            }
            if (customQueryParam instanceof WithValueQueryParam) {
                WithValueQueryParam withValueQueryParam = (WithValueQueryParam) customQueryParam;
                dataMap.put(dataMap.size() + "", withValueQueryParam.getValue());
                // 不能以property为key放入dataMap，因为可能有相同的property，如> & < 
                condition = condition + property.getColumnName() + " " + withValueQueryParam.getOperator() + " #{" + (dataMap.size() - 1) + "}";
//                i++; 
            } else if (customQueryParam instanceof NoValueQueryParam) {
                NoValueQueryParam noValueQueryParam = (NoValueQueryParam) customQueryParam;
                condition = condition + property.getColumnName() + " " + noValueQueryParam.getCondition();
            }
            sql.WHERE(condition);
        }
        return sql;
    }

    public String updateFields(ProviderContext context, Map<String, Object> param) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
        initModelByContext(context);
        String[] fields = (String[]) param.get(BaseMapper.PARAM_FIELDS);
        return new UpdateSql(fields, true, false).toString();
    }

    public String updateFieldsWithoutFlag(ProviderContext context, Map<String, Object> param) {
        initModelByContext(context);
        String[] fields = (String[]) param.get(BaseMapper.PARAM_FIELDS);
        return new UpdateSql(fields, false, false).toString();
    }

    public String updateFieldsWithFlagCond(ProviderContext context, Map<String, Object> param) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
        initModelByContext(context);
        String[] fields = (String[]) param.get(BaseMapper.PARAM_FIELDS);
        return new UpdateSql(fields, true, true).toString();
    }

    public String batchUpdateFieldsByPks(ProviderContext context, Map<String, Object> param) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
        initModelByContext(context);
        String[] fields = (String[]) param.get(BaseMapper.PARAM_FIELDS);
        String[] pks = (String[]) param.get(BaseMapper.PARAM_PKS);
        return new batchUpdateSql(fields, true, false,pks).toString();
    }

    private class UpdateSql extends SQL {
        UpdateSql(String[] fields, boolean updateChangeFlag, boolean flagCond) {
            UPDATE(tableName);
            for (String field : fields) {
                Class retType = BeanHelper.getFieldTypeFromClz(modelClass, field);
                SET(BeanHelper.getColName(modelClass, field) + " = #{" + BaseMapper.PARAM_VO + "." + field
                        + ",javaType=" + retType.getName() + "}");
            }
            if (updateChangeFlag) {
                // 这里不生成changeFlag
                SET("change_flag = #{" + BaseMapper.PARAM_VO + ".changeFlag}");
            }
            WHERE(BeanHelper.getIdColName(modelClass)
                    + " = #{" + BaseMapper.PARAM_VO + "." + BeanHelper.getIdFieldName(modelClass) + "} ");
            if (flagCond) {
                WHERE("change_flag = #{origChangeFlag}");
            }
        }
    }

    private class batchUpdateSql extends SQL {
        batchUpdateSql(String[] fields, boolean updateChangeFlag, boolean flagCond, String[] pks) {
            UPDATE(tableName);
            for (String field : fields) {
                Class retType = BeanHelper.getFieldTypeFromClz(modelClass, field);
                SET(BeanHelper.getColName(modelClass, field) + " = #{" + BaseMapper.PARAM_VO + "." + field
                        + ",javaType=" + retType.getName() + "}");
            }
            if (updateChangeFlag) {
                // 批量修改chanageFlag 以第一个对象的changeFlag做为标准
                SET("change_flag = #{" + BaseMapper.PARAM_VO + ".changeFlag}");
            }
            WHERE(BeanHelper.getIdColName(modelClass)
                    + " in (" + getPksSql(pks) + ") ");
            if (flagCond) {
                WHERE("change_flag = #{origChangeFlag}");
            }
        }
    }

    private String getPksSql(String[] pks) {
        StringBuffer sql = new StringBuffer();
        for (String pk : pks) {
            sql.append("'"+pk+"'");
            sql.append(",");
        }
        String finalSql = sql.substring(0, sql.length() - 1);
        return finalSql;
    }

    private void initModelByContext(ProviderContext context) {
        ParameterizedType type = (ParameterizedType) context.getMapperType().getGenericInterfaces()[0];
        modelClass = (Class<?>) type.getActualTypeArguments()[0];
        tableName = BeanHelper.getTableName(modelClass);
    }
}